<?php
declare (strict_types = 1);

namespace app\access\middleware;

use thans\jwt\exception\JWTException;
use thans\jwt\facade\JWTAuth;
use think\exception\HttpException;
use think\exception\ValidateException;

/**
 * 练习至少3遍以上、把所有的错误以及处理逻辑
 * 后台管理接口安全
 * 1. token(凭证)
 * 登录成功之后颁发的认证
 * 验证token (需要登录成功之后需要访问接口)
 * 2.时间戳机制 （一个请求从客户端到服务端请求时间、进行检测）
 * 3.请求频率限制（限制的路由、也就是控制器/方法）
 * 4.权限检测（检测用户是否有权限访问、检测控制器/方法）
 * 5.签名机制（把请求参数按照一定规则进行排序、处理、加密生成的一个字符串）请求数据（例如：添加商品 goods_name）
 * （客户端生成一个签名、服务端也生成一个签名、两个进行对不）如果签名不一致、返回请求失败、签名一致、请求才有效
 * 备注：每次请求的签名是不一致的
 *
 * Class CheckAccess
 * @package app\access\middleware
 */
class CheckAccess
{
    /**
     * 处理请求
     *
     * @param \think\Request $request
     * @param \Closure       $next
     * @return Response
     */
    public function handle($request, \Closure $next)
    {


        try {
            // 使用的过程中 。。。 报错 must to have token token is expirse
            $user_id = JWTAuth::auth()['id']->getValue();//获取自定义参数数据
            if (!$user_id) {
                return  fail(2002,'请登录');
            }
            return $next($request);
            // 签名验证
            $this->checkSign();

            return $next($request);
        }catch (JWTException $JWTException) {
           return  fail($JWTException->getCode()?:2001,$JWTException->getMessage());
        }catch (ValidateException $exception) {
            return  fail($exception->getCode()?:2001,$exception->getMessage());
        }catch (HttpException $httpException) {
            return  fail($httpException->getCode()?:2001,$httpException->getMessage());
        }


    }

    /**
     * 验签
     * 客户端与服务端按照一定规则生成
     */
    protected function checkSign(){
        $client_sign = request()->all('sign');# 客户端
        $serve_sign =  $this->createSign(); # 服务端生成

        if ($client_sign != $serve_sign) {
            abort(2001,'签名错误');
        }

    }


    /**
     * 服务端生成签名、防止数据被篡改
     * 对所有API请求参数（包括公共参数和业务参数，但除去sign参数和byte[]类型的参数），根据参数名称的ASCII码表的顺序排序。如：foo:1, bar:2, foo_bar:3, foobar:4排序后的顺序是bar:2, foo:1, foo_bar:3, foobar:4。
    将排序好的参数名和参数值拼装在一起，根据上面的示例得到的结果为：bar2foo1foo_bar3foobar4。
    把拼装好的字符串采用utf-8编码，使用签名算法对编码后的字节流进行摘要。如果使用MD5算法，则需要在拼装的字符串前后加上app的secret后，再进行摘要，如：md5(secret+bar2foo1foo_bar3foobar4+secret)；如果使用HMAC_MD5算法，则需要用app的secret初始化摘要算法后，再进行摘要，如：hmac_md5(bar2foo1foo_bar3foobar4)。
    将摘要得到的字节流结果使用十六进制表示，如：hex("helloworld".getBytes("utf-8")) = "68656C6C6F776F726C64"
     */
    protected function createSign(){
        $param = request()->all();
        unset($param['sign']);//去掉签名的参数
        ksort($param);// 根据键名排序
        $str = '';
        foreach ($param as $key => $value) {
            $str.=$key.'='.$value;
        }
//        $str = base64_encode($str);
//        $sign = sha1($str);
        # 也可以加盐
        $sign = md5($str);

        return $sign;
    }

}
